package fs

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"runtime"
)

const blockSize = 8192

func Walk(path string) (<-chan Fs, error) {
	d, err := os.Stat(path)
	if err != nil {
		return nil, err
	}
	if !d.IsDir() {
		return nil, fmt.Errorf("%q is not a directory", path)
	}
	c := make(chan Fs)
	go walker(path, d, c)
	return c, nil
}

func walker(path string, d os.FileInfo, c chan<- Fs) {
	reply := make(chan Reply)
	c <- Fs{Info: d, Reply: reply}
	if <-reply == Down {
		walkdir(path, c)
	}
	close(c)
}

func walkdir(path string, c chan<- Fs) {
	reply := make(chan Reply)
	a, err := ioutil.ReadDir(path)
	if err != nil {
		report("cannot readdir %q: %v", path, err)
		return
	}
	for _, d := range a {
		if d.IsDir() {
			c <- Fs{Info: d, Reply: reply}
			switch <-reply {
			case Quit:
				quit()
			case Down:
				walkdir(filepath.Join(path, d.Name()), c)
				c <- Fs{Reply: reply}
				if <-reply == Quit {
					quit()
				}
			case Skip:
				return
			}
		} else {
			if walkfile(path, d, c) == Skip {
				return
			}
		}
	}
}

func walkfile(path string, d os.FileInfo, c chan<- Fs) Reply {
	f, err := os.Open(path)
	if err != nil {
		report("cannot open %q: %v", path, err)
		return Next
	}
	reply := make(chan Reply)
	c <- Fs{Info: d, Reply: reply}
	switch <-reply {
	case Quit:
		quit()
	case Skip:
		return Skip
	case Next:
		return Next
	case Down:
		break
	}
	length := d.Size()
	for n := int64(0); n < length; {
		nr := blockSize
		if n+int64(blockSize) > length {
			nr = int(length - n)
		}
		buf := make([]byte, nr)
		nr, err := f.Read(buf)
		if err != nil {
			if err == io.EOF {
				report("%q is shorter than expected (%d/%d)", path, n, length)
			} else {
				report("error reading %q: %v", path, err)
			}
			break
		} else if nr < len(buf) {
			buf = buf[0:nr]
		}
		c <- Fs{Data: buf, Reply: reply}
		switch <-reply {
		case Quit:
			quit()
		case Skip:
			return Next
		}
		n += int64(nr)
	}
	c <- Fs{Reply: reply}
	if <-reply == Quit {
		quit()
	}
	return Next
}

func report(f string, args ...interface{}) {
	log.Printf(f, args...)
}

func quit() {
	runtime.Goexit()
}
